package com.lagou.edu.factory;

import com.lagou.edu.annotation.ZdyAutowired;
import com.lagou.edu.annotation.ZdyService;
import com.lagou.edu.annotation.ZdyTransactional;
import com.mysql.jdbc.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.reflections.Reflections;

import javax.management.relation.Relation;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author 应癫
 *
 * 工厂类，生产对象（使用反射技术）
 */
public class BeanFactory {

 /*
 * 1.扫描包下所有的@service，通过反射完成对象实例化
 * 2.根据@Autowired完成注解的依赖关系
 * 3.针对@Transactional，修改对象为对应的代理对象
 * */
    private static Map<String,Object> map = new HashMap<>();
    /**
     * 声明静态方法，一开始就加载
     * 方法一： 扫描注解解析
     */
    static {
        //扫描获取反射对象集合
        try {
        //扫描包，通过反射技术实例化对象并且存储待用（map集合）
            Reflections reflections = new Reflections("com.lagou.edu");
            Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(ZdyService.class);
            for (Class<?> aClass : typesAnnotatedWith) {
                // 通过反射技术实例化对象
                Object bean = aClass.newInstance();
                ZdyService annotation = aClass.getAnnotation(ZdyService.class);
                //对象ID在service注解有value时用value，没有时用类名
                if (StringUtils.isNullOrEmpty(annotation.value())){
                    //由于getName获取的是全限定类名，所以要分割去掉前面包名部分
                    String[] names = aClass.getName().split("\\.");
                    map.put(toLowerCase(names[names.length-1]),bean);
                }else {
                    map.put(annotation.value(),bean);
                }
            }
            // 实例化完成之后维护对象的依赖关系Autowired，检查哪些对象需要传值进入，
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                Object value = entry.getValue();
                Class aClass = value.getClass();
                //获取所有的变量
                Field[] fields = aClass.getDeclaredFields();
                //遍历属性，若持有Autowired注解则注入
                for (Field field : fields) {
                    //判断是否是使用注解的参数
                    if (field.isAnnotationPresent(ZdyAutowired.class)
                       && field.getAnnotation(ZdyAutowired.class).required()){
                        String[] names = field.getType().getName().split("\\.");
                        String name = toLowerCase(names[names.length - 1]);
                        //Autowired注解的位置需要set方法，方便c.getMethods()获取
                        Method[] methods = aClass.getMethods();
                        for (int i = 0; i < methods.length; i++) {//TransactionManager
                            Method method = methods[i];
                            if(method.getName().equalsIgnoreCase("set"+name)){
                                method.invoke(value,map.get(name));
                            }
                        }
                    }
                }
            }

            for (Map.Entry<String, Object> entry : map.entrySet()) {
                Object value = entry.getValue();
                Class aClass = value.getClass();
                //判断对象类是否持有Transactional注解，若有则修改对象为代理对象
                if(aClass.isAnnotationPresent(ZdyTransactional.class)){
                    //获取代理工厂
                    ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
                    //获取类c实现的所有接口
                    Class[] interfaces = aClass.getInterfaces();
                    //判断对象是否实现接口
                    //没实现使用CGLIB
                    if(interfaces!=null && interfaces.length>0){
                        //实现使用JDK
                        value = proxyFactory.getJdkProxy(value);
                    }else {
                        value = proxyFactory.getCglibProxy(value);
                    }
                }

                // 把处理之后的object重新放到map中
                map.put(entry.getKey(),value);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //任务二：对外提供获取实例对象的接口（根据id获取）
    public static Object getBean(String id){
        return map.get(id);
    }

    public static String toLowerCase(String value){
        char[] chars = value.toCharArray();
        chars[0]+=32;
        return String.valueOf(chars);
    }

    /**
     * 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */
   /* static {
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> beanList = rootElement.selectNodes("//bean");
            for (int i = 0; i < beanList.size(); i++) {
                Element element = beanList.get(i);
                String id = element.attributeValue("id");
                String aClass = element.attributeValue("class");
                Class<?> aClass1 = Class.forName(aClass);
                Object o = aClass1.newInstance();// 实例化之后的对象
                map.put(id,o);
            }
            // 实例化完成之后维护对象的依赖关系，检查哪些对象需要传值进入，根据它的配置，我们传入相应的值
            // 有property子元素的bean就有传值需求
            List<Element> propertyList = rootElement.selectNodes("//property");
            // 解析property，获取父元素
            for (int i = 0; i < propertyList.size(); i++) {
                Element element = propertyList.get(i);
                String name = element.attributeValue("name");
                String ref = element.attributeValue("ref");
                // 找到当前需要被处理依赖关系的bean
                Element parent = element.getParent();
                // 调用父元素对象的反射功能
                String parentId = parent.attributeValue("id");
                Object parentObject = map.get(parentId);
                // 遍历父对象中的所有方法，找到"set" + name
                Method[] methods = parentObject.getClass().getMethods();
                for (int j = 0; j < methods.length ; j++) {
                    Method method = methods[j];
                    if(method.getName().equalsIgnoreCase("set"+name)){
                        method.invoke(parentObject, map.get(ref));
                    }
                }
                map.put(parentId,parentObject);
            }

        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
*/



}
